﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX.Direct3D10;
using System.Runtime.InteropServices;
using SlimDX;
using SlimDX.DXGI;

namespace System.Windows.Media.Media3D10
{
    /// <summary>
    /// Modèle 3D pour un sprite
    /// </summary>
    public sealed class SpriteModel3D10 : RenderModel3D10
    {
        #region Dependency Properties
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="Width"/>.
        /// </summary>
        public static readonly DependencyProperty WidthProperty =
            DependencyProperty.Register("Width", typeof(float), typeof(SpriteModel3D10),
            new PropertyMetadata(new PropertyChangedCallback(Width_PropertyChanged)));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="Height"/>.
        /// </summary>
        public static readonly DependencyProperty HeightProperty =
            DependencyProperty.Register("Height", typeof(float), typeof(SpriteModel3D10),
            new PropertyMetadata(new PropertyChangedCallback(Height_PropertyChanged)));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="Opacity"/>.
        /// </summary>
        public static readonly DependencyProperty OpacityProperty =
            DependencyProperty.Register("Opacity", typeof(float), typeof(SpriteModel3D10),
            new PropertyMetadata(1.0f, new PropertyChangedCallback(Opacity_PropertyChanged)));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="Brush"/>.
        /// </summary>
        public static readonly DependencyProperty BrushProperty =
            DependencyProperty.Register("Brush", typeof(Brush), typeof(SpriteModel3D10),
            new PropertyMetadata(new PropertyChangedCallback(Brush_PropertyChanged)));
        #endregion

        #region Dependency Properties Callbacks
        private static void Width_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(sender, null))
                throw new ArgumentNullException("sender");
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            (sender as SpriteModel3D10).OnWidthChanged(e);
        }

        private static void Height_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(sender, null))
                throw new ArgumentNullException("sender");
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            (sender as SpriteModel3D10).OnHeightChanged(e);
        }

        private static void Opacity_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(sender, null))
                throw new ArgumentNullException("sender");
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            (sender as SpriteModel3D10).OnOpacityChanged(e);
        }

        private static void Brush_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(sender, null))
                throw new ArgumentNullException("sender");
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            (sender as SpriteModel3D10).OnBrushChanged(e);
        }
        #endregion
        
        #region Data
        private static readonly SlimDX.Direct3D10.Buffer _vertexBuffer = CreateVertexBuffer();
        private static readonly InputElement[] _inputElements = new[] 
            {
                new InputElement("POSITION", 0, Format.R32G32B32_Float, 0, 0)
            };

        private const string g_spriteWidth = "g_spriteWidth";
        private const string g_spriteHeight = "g_spriteHeight";
        private const string g_opacity = "g_opacity";
        private const string sprite_texture = "sprite_texture";

        private EffectScalarVariable _widthVariable;
        private EffectScalarVariable _heightVariable;
        private EffectScalarVariable _opacityVariable;
        private EffectResourceVariable _brushVariable;
        #endregion

        #region Public Properties
        /// <summary>
        /// Largueur du sprite
        /// </summary>
        public float Width
        {
            get { return (float)GetValue(WidthProperty); }
            set { SetValue(WidthProperty, value); }
        }

        /// <summary>
        /// Hauteur du sprite
        /// </summary>
        public float Height
        {
            get { return (float)GetValue(HeightProperty); }
            set { SetValue(HeightProperty, value); }
        }

        /// <summary>
        /// Opacité du sprite
        /// </summary>
        public float Opacity
        {
            get { return (float)GetValue(OpacityProperty); }
            set { SetValue(OpacityProperty, value); }
        }

        /// <summary>
        /// Texture à appliquer
        /// </summary>
        public Brush Brush
        {
            get { return (Brush)GetValue(BrushProperty); }
            set { SetValue(BrushProperty, value); }
        }
        #endregion

        #region Private Members
        private void OnWidthChanged(DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            if (_widthVariable != null) _widthVariable.Set(this.Width);

        }

        private void OnHeightChanged(DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            if (_heightVariable != null) _heightVariable.Set(this.Height);

        }

        private void OnOpacityChanged(DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            if (_opacityVariable != null) _opacityVariable.Set(this.Opacity);

        }

        private void OnBrushChanged(DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            if (_brushVariable != null) _brushVariable.SetTexture(this.Brush);
        }

        private static SlimDX.Direct3D10.Buffer CreateVertexBuffer()
        { 
            Vector3Collection vertex = new Vector3Collection() { SlimDX.Vector3.Zero };
            vertex.Freeze();
            SlimDX.Direct3D10.Buffer buffer = vertex.Buffer;
            return buffer;
        }
        #endregion

        #region Model3D10 Overrides
        /// <summary>
        /// Appelée lors du changement de l'effet
        /// </summary>
        /// <param name="e"><see cref="DependencyPropertyChangedEventArgs"/> décrivant les informations sur le changement.</param>
        protected override void OnEffectChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnEffectChanged(e);
            if (this.Effect != null)
            {
                _widthVariable = this.Effect.GetVariableByName(g_spriteWidth).AsScalar();
                _heightVariable = this.Effect.GetVariableByName(g_spriteHeight).AsScalar();
                _opacityVariable = this.Effect.GetVariableByName(g_opacity).AsScalar();
                _brushVariable = this.Effect.GetVariableByName(sprite_texture).AsResource();
                _widthVariable.Set(this.Width);
                _heightVariable.Set(this.Height);
                _opacityVariable.Set(this.Opacity);
                _brushVariable.SetTexture(this.Brush);
            }
            else
            {
                _widthVariable = null;
                _heightVariable = null;
                _opacityVariable = null;
                _brushVariable = null;
            }
        }

        /// <summary>
        /// En cas de substitution dans une classe dérivée, participe au dessin des objets dans le pipeline DirectX10
        /// </summary>
        /// <param name="context">Contexte de dessin.</param>
        /// <exception cref="ArgumentNullException">Soulevée si le contexte est null.</exception>
        protected override void OnDraw(DrawingContext context)
        {
            if (context == null) throw new ArgumentNullException("context");

            base.OnDraw(context);

            context.Draw(this, PrimitiveTopology.PointList, 1);
        }
        #endregion

        #region RenderModel3D10 Implementation
        /// <summary>
        /// Crée le <see cref="InputLayout"/> associé à ce <see cref="RenderModel3D10"/>
        /// </summary>
        /// <returns></returns>
        protected override SlimDX.Direct3D10.InputLayout CreateInputLayoutCore()
        {
            return CurrentEffectTechnique == null ? null : CurrentEffectTechnique.CreateInputLayout(_inputElements);
        }

        /// <summary>
        /// Crée le <see cref="VertexBufferBinding"/> associé à ce <see cref="RenderModel3D10"/>
        /// </summary>
        /// <returns></returns>
        protected override SlimDX.Direct3D10.VertexBufferBinding[] CreateVertexBufferBindingCore()
        {
            VertexBufferBinding[] binding = new[]
            {
                new VertexBufferBinding(_vertexBuffer, Marshal.SizeOf(typeof(Vector3)), 0)
            };
            return binding;
        }
        #endregion

        #region Freezable Implementation
        /// <summary>
        /// Une fois implémentée dans une classe dérivée, initialise une nouvelle instance de la classe dérivée <see cref="Freezable"/>. 
        /// </summary>
        /// <returns>Nouvelle instance.</returns>
        protected override Freezable CreateInstanceCore()
        {
            return new SpriteModel3D10();
        }
        #endregion
    }
}
